package org.bookie.hibernate;

import java.io.InputStream;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.ServiceImplementationFactory;
import org.apache.hivemind.ServiceImplementationFactoryParameters;
import org.apache.hivemind.ShutdownCoordinator;
import org.apache.hivemind.events.RegistryShutdownListener;
import org.apache.hivemind.service.ThreadEventNotifier;
import org.bookie.hibernate.config.ConfigElement;
import org.bookie.hibernate.config.Configurator;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;

/**
 * {@link ServiceImplementationFactory} for creating new Hibernate {@link Session}
 * proxies. This factory takes care that created {@link Session} instances are
 * associated and listen to the conversational events.
 * 
 * @author Marcus Schulte
 * @author James Carman
 * @author zubairov
 */
public class HibernateSessionFactory implements ServiceImplementationFactory,
		RegistryShutdownListener {
	
	private SessionFactory sessionFactory;

	protected Log log;

	private AnnotationConfiguration config;

	private ShutdownCoordinator shutdownCoord;

	private ThreadEventNotifier threadEventNotifier;

	/**
	 * Constructor with configurations and {@link ShutdownCoordinator}
	 * 
	 * @param contributed
	 * @param sc
	 */
	public HibernateSessionFactory(List<ConfigElement> contributed, ShutdownCoordinator sc, ThreadEventNotifier notifier) {
		shutdownCoord = sc;
		threadEventNotifier = notifier;
		config = new AnnotationConfiguration() {

			private static final long serialVersionUID = 1L;

			/**
			 * Make sure, we use the current Classloader for Configs ...
			 * 
			 * @see net.sf.hibernate.cfg.Configuration#getConfigurationInputStream(java.lang.String)
			 */
			protected InputStream getConfigurationInputStream(String resource)
					throws HibernateException {
				InputStream stream = null;
				Thread.currentThread().getContextClassLoader()
						.getResourceAsStream(resource);
				stream = getClass().getResourceAsStream(resource);
				if (stream == null) {
					throw new HibernateException(resource + " not found");
				}
				return stream;
			}

		};
		Configurator configuratior = new Configurator(contributed);
		configuratior.applyTo(config);
		shutdownCoord.addRegistryShutdownListener(this);
	}

	/**
	 * Initialization method called by Hivemind when service is instantiated
	 */
	public void init() {
		try {
			log.debug("Initializing Hibernate SessionFactory...");
			log.debug("Updating database schema...");
			
			new SchemaUpdate(config).execute(true, true);
			sessionFactory = config.buildSessionFactory();
		} catch (Exception e) {
			throw new ApplicationRuntimeException(e);
		}
	}

	public Object createCoreServiceImplementation(
			ServiceImplementationFactoryParameters params) {
		try {
			log.debug("Creating Hibernate Session...");
			Session session = sessionFactory.openSession();
			threadEventNotifier.addThreadCleanupListener(new SessionCloserAdapter(session));
			return session;
		} catch (Exception e) {
			throw new ApplicationRuntimeException(e);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void registryDidShutdown() {
		try {
			log.debug("Closing Hibernate SessionFactory...");
			sessionFactory.close();
		} catch (HibernateException e) {
			throw new ApplicationRuntimeException(e);
		}
	}

	/**
	 * @category injector
	 * @param log
	 */
	public void setLog(Log log) {
		this.log = log;
	}
}
